/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.alertnetusa.validator;

import com.incident.cap.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.ListIterator;
import java.util.StringTokenizer;
/** 
 * A CAP 1.1 or CAP 1.0 message.  May have one or more EasCapInfo objects attached.<p>
 * <i>Copyright (c) 2008-2009 Thai Van<p>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * @see EasCapInfo
 * @author Thai Van, Prafash Kumar. This class is based on Art Botterell, David R Robinson work on com.incident.cap.Alert
 * @version 1 Oct 2008
 */
public class EasCapAlert {

    /** Code version */
    protected static String version = "2.0.4";
     /** Ability to indent an XML fragment */
    protected static String indentString = "  ";
    /** Use to parse CAP message */
    protected static EasCapParser parser = new EasCapParser();
    /** String buffer for use through out this class */
    private StringBuffer buf;
    /** CAP name space */
    protected String nameSpace = "urn:oasis:names:tc:emergency:cap:1.1";
    /** CAP style sheet name */
    protected String stylesheetName = null;               // Stylesheet processing instruction values
    /** CAP style sheet type */
    protected String stylesheetType = "text/xsl";
     /** CAP identifier */
    protected String identifier = null;                   // Message ID
    /** CAP sender */
    protected String sender = null;                       // Sender ID
    /** CAP sent */
    protected String sent = null;                         // Date Time Sent
    /** CAP status */
    protected String status = null;                       // Status
    /** CAP msgType */
    protected String msgType = null;                      // Message Type
    /** CAP password */
    protected String password = null;                     // Password
    /** CAP source */
    protected String source = null;                       // Operator/Device ID
    /** CAP scope */
    protected String scope = null;                        // Scope
    /** CAP restriction */
    protected String restriction = null;
    /** CAP addresses */
    protected String addresses = null;
    /** CAP ArrayList of codes */
    protected ArrayList codes = new ArrayList();
    /** CAP ListIterator of codes for easy traversal */
    protected ListIterator codesListIterator = codes.listIterator();
    /** CAP note */
    protected String note = null;                           // Reference IDs
    /** CAP references */
    protected String references = null;                     // Incident IDs
    /** CAP incidents */
    protected String incidents = null;                      // EasCapInfo Blocks (ArrayList of EasCapInfo objects)
    /** CAP ArrayList of infos */
    protected ArrayList infos = new ArrayList();
    /** CAP ListIterator of infos for easy traversal */
    protected ListIterator infosListIterator = infos.listIterator();

    /** return version of this library */
    public static String getVersion() {
        return version;
    }

    /** return EasCapAlert object from a CAP message at a URI  */
    public static EasCapAlert getAlert(URI uri) {
        return parser.parseFromUri(uri);
    }

    /** return EasCapAlert object from a CAP message string  */
    public static EasCapAlert parse(String cap) {
        return parser.parseFromString(cap);
    }

    /** return EasCapAlert object from a CAP message file */
    public static EasCapAlert parseFile(File file) {
        return parse(loadFile(file));
    }

    /** Create EasCapAlert object from a CAP message string
     * @deprecated Use parse() instead
     */
    public static EasCapAlert unmarshal(String cap) {
        return parse(cap);
    }

    /** Serialize this EasCapAlert object as CAP 1.1 XML
     * @deprecated Use toString() or toCAP10() instead
     */
    public String marshal() {
        return toString();
    }

    /** Get message Stylesheet name */
    public String getStylesheetName() {
        return stylesheetName;
    }

    /** set stylesheet name (default is null for no stylesheet) */
    public void setStylesheetName(String value) {
        stylesheetName = value;
    }

    /** Get message Stylesheet MIME type */
    public String getStylesheetType() {
        return stylesheetType;
    }

    /** set stylesheet MIME type */
    public void setStylesheetType(String value) {
        stylesheetType = value;
    }

    /** Change or eliminate XML indenting */
    public static void setIndentString(String iStr) {
        indentString = iStr;
    }

    /** Get message identifer */
    public String getIdentifier() {
        return identifier;
    }

    /** Set message identifer */
    public void setIdentifier(String value) {
        identifier = value;
    }

    /** Get sender identifer */
    public String getSender() {
        return sender;
    }

    /** Set sender identifer */
    public void setSender(String value) {
        sender = value;
    }

    /** Get sent date/time  */
    public String getSent() {
        return sent;
    }

    /** Set sent date/time  (in dateTime format) */
    public void setSent(String value) {
        sent = value;
    }

    /** Get message status  */
    public String getStatus() {
        return status;
    }

    /** Set message status ( Actual / Exercise / System / Test )  */
    public void setStatus(String value) {
        status = value;
    }

    /** Get message type  */
    public String getMsgType() {
        return msgType;
    }

    /** Set message type */
    public void setMsgType(String value) {
        msgType = value;
    }

    /** Get password   
     * @deprecated Not supported in CAP 1.1 and a security risk in CAP 1.0 */
    public String getPassword() {
        return password;
    }

    /** Set password 
     * @deprecated Not supported in CAP 1.1 and a security risk in CAP 1.0 */
    public void setPassword(String value) {
        password = value;
    }

    /** Get Operator/Device ID  */
    public String getSource() {
        return source;
    }

    /** Set Operator/Device ID  */
    public void setSource(String value) {
        source = value;
    }

    /** Get message scope  */
    public String getScope() {
        return scope;
    }

    /** Set message scope  ( Public / Restricted / Private ) */
    public void setScope(String value) {
        scope = value;
    }    // Restriction

    /** Get Restriction  */
    public String getRestriction() {
        return restriction;
    }

    /** Set Restriction (used if Scope is "Restricted")  */
    public void setRestriction(String value) {
        restriction = value;
    }

    /** Get Addresses  */
    public String getAddresses() {
        return addresses;
    }

    /** Set Addresses (used if Scope is "Private")  */
    public void setAddresses(String value) {
        addresses = value;
    }

    /** Append a supplied Handling code  to this EasCapAlert  */
    public void addCode(String value) {
        while (hasNextCode()) {
            try {
                getNextCode();
            } catch (Exception e) {
            }
        } // move to end of list
        codesListIterator.add(value);
    }

    /** Return number of Handling Codes in this EasCapAlert  */
    public int countCodes() {
        return codes.size();
    }

    /** Test if there are more items in the iterator */
    public boolean hasNextCode() {
        return codesListIterator.hasNext();
    }

    /** Return the next Handling Code from the iterator */
    public String getNextCode() {
        if (hasNextCode()) {
            return (String) codesListIterator.next();
        } else {
            return null;
        }
    }

    /** Remove Handling Code most recently returned by getNextCode() */
    public void removeCode() {
        codesListIterator.remove();
    }

    /** Reset to index position in Handling Code iterator */
    public void resetCodes() {
        codesListIterator = codes.listIterator();
    }

    /** Get Note  */
    public String getNote() {
        return note;
    }

    /** Set Note */
    public void setNote(String value) {
        note = value;
    }

    /** Get Reference IDs  */
    public String getReferences() {
        return references;
    }

    /** Set Reference IDs  */
    public void setReferences(String value) {
        references = value;
    }

    /** Get Incident IDs  */
    public String getIncidents() {
        return incidents;
    }

    /** Set Incident IDs  */
    public void setIncidents(String value) {
        incidents = value;
    }

    /** Append a supplied EasCapInfo object and return a reference to it */
    public EasCapInfo addInfo(EasCapInfo info) {
        while (hasNextInfo()) {
            try {
                getNextInfo();
            } catch (Exception e) {
            }
        } // move to end of list
        infosListIterator.add(info);
        return info;
    }

    /** Append an empty EasCapInfo object and return a reference to it */
    public EasCapInfo addInfo() {
        while (hasNextInfo()) {
            try {
                getNextInfo();
            } catch (Exception e) {
            }
        } // move to end of list
        EasCapInfo info = new EasCapInfo();
        infosListIterator.add(info);
        return info;
    }

    /** Return number of EasCapInfo objects in this EasCapAlert  */
    public int countInfos() {
        return infos.size();
    }

    /** Test if there are more items in the iterator */
    public boolean hasNextInfo() {
        return infosListIterator.hasNext();
    }

    /** Return the next EasCapInfo from the iterator */
    public EasCapInfo getNextInfo() {
        if (hasNextInfo()) {
            return (EasCapInfo) infosListIterator.next();
        } else {
            return null;
        }
    }

    /** Remove EasCapInfo most recently returned by getNextInfo() */
    public void removeInfo() {
        infosListIterator.remove();
    }

    /** Reset to index position in EasCapInfo iterator */
    public void resetInfos() {
        infosListIterator = infos.listIterator();
    }
    // UTILITY INSTANCE METHODS
    /** Returns the <references> value that refers to this EasCapAlert */
    public String getReferenceValue() {
        return getSender() + "," + getIdentifier() + "," + getSent();
    }

    /** Return the bounding box (latMax, lonMin latMin,lonMax) for this EasCapAlert */
    public String getBounds() {
        String bounds = "";
        double latMax = -90.0;
        double latMin = 90.0;
        double lonMax = -180.0;
        double lonMin = 180.0;
        resetInfos();
        while (hasNextInfo()) {
            try {
                String thisBox = getNextInfo().getBounds();
                StringTokenizer st = new StringTokenizer(thisBox);
                String vertex = st.nextToken();
                StringTokenizer st2 = new StringTokenizer(vertex, ",");
                double thisLatMax = Double.parseDouble(st2.nextToken());
                double thisLonMin = Double.parseDouble(st2.nextToken());
                vertex = st.nextToken();
                st2 = new StringTokenizer(vertex, ",");
                double thisLatMin = Double.parseDouble(st2.nextToken());
                double thisLonMax = Double.parseDouble(st2.nextToken());
                if (thisLatMax > latMax) {
                    latMax = thisLatMax;
                }
                if (thisLatMin < latMin) {
                    latMin = thisLatMin;
                }
                if (thisLonMax > lonMax) {
                    lonMax = thisLonMax;
                }
                if (thisLonMin < lonMin) {
                    lonMin = thisLonMin;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        bounds = Double.toString(latMax) + "," + Double.toString(lonMin) + " " + Double.toString(latMin) + "," + Double.toString(lonMax);
        return bounds;
    }

    /** return raw EAS-CAP message */
    public String toEASCAP() {
        buf = new StringBuffer();

        if (identifier != null) {
            buf.append("<identifier>" + entities(identifier) + "</identifier>\n");
        }

        if (sender != null) {
            buf.append("<sender>" + entities(sender) + "</sender>\n");
        }

        if (sent != null) {
            buf.append("<sent>" + entities(sent) + "</sent>\n");
        }

        if (status != null) {
            buf.append("<status>" + entities(status) + "</status>\n");
        }

        if (msgType != null) {
            buf.append("<msgType>" + entities(msgType) + "</msgType>\n");
        }

        if (source != null) {
            buf.append("<source>" + entities(source) + "</source>\n");
        }

        if (scope != null) {
            buf.append("<scope>" + entities(scope) + "</scope>\n");
        }

        if (restriction != null) {
            buf.append("<restriction>" + entities(restriction) + "</restriction>\n");
        }

        if (addresses != null) {
            buf.append("<addresses>" + entities(addresses) + "</addresses>\n");
        }

        resetCodes();
        while (hasNextCode()) {
            buf.append("<code>" + entities(getNextCode()) + "</code>\n");
        }

        if (note != null) {
            buf.append("<note>" + entities(note) + "</note>\n");
        }

        if (references != null) {
            buf.append("<references>" + entities(references) + "</references>\n");
        }

        if (incidents != null) {
            buf.append("<incidents>" + entities(incidents) + "</incidents>\n");
        }

        // EasCapInfo blocks
        resetInfos();
        if (hasNextInfo()) {
            buf.append("<info>\n" + indent(getNextInfo().toEASCAP()) + "</info>\n");
        }

        // wrap the StringBuffer contents in the XML header and <alert> tags
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        if (stylesheetName != null) {
            xml = xml + "<?xml-stylesheet href=\"" + stylesheetName + "\" type=\"" + stylesheetType + "\"?>\n";
        }
        xml = xml + "<alert xmlns=\"" + nameSpace + "\">\n";
        xml = xml + indent(buf.toString());
        xml = xml + "</alert>\n";
        return xml;
    }
    
    /** Overridden toString() to return XML message */
    public String toString() {
        buf = new StringBuffer();

        if (identifier != null) {
            buf.append("<identifier>" + entities(identifier) + "</identifier>\n");
        } else {
            buf.append("<identifier>" + entities(String.valueOf(new Date().getTime())) + "</identifier>\n");
        }

        if (sender != null) {
            buf.append("<sender>" + entities(sender) + "</sender>\n");
        } else {
            buf.append("<sender>" + entities("Unknown") + "</sender>\n");
        }

        if (sent != null) {
            buf.append("<sent>" + entities(sent) + "</sent>\n");
        } else {
            buf.append("<sent>" + entities(DateTime.getDateTime()) + "</sent>\n");
        }

        if (status != null) {
            buf.append("<status>" + entities(status) + "</status>\n");
        } else {
            buf.append("<status>" + entities("Test") + "</status>\n");
        }

        if (msgType != null) {
            buf.append("<msgType>" + entities(msgType) + "</msgType>\n");
        } else {
            buf.append("<msgType>" + entities("Alert") + "</msgType>\n");
        }

        if (source != null) {
            buf.append("<source>" + entities(source) + "</source>\n");
        }

        if (scope != null) {
            buf.append("<scope>" + entities(scope) + "</scope>\n");
        } else {
            buf.append("<scope>" + entities("Public") + "</scope>\n");
        }

        if (restriction != null) {
            buf.append("<restriction>" + entities(restriction) + "</restriction>\n");
        }

        if (addresses != null) {
            buf.append("<addresses>" + entities(addresses) + "</addresses>\n");
        }

        resetCodes();
        while (hasNextCode()) {
            buf.append("<code>" + entities(getNextCode()) + "</code>\n");
        }

        if (note != null) {
            buf.append("<note>" + entities(note) + "</note>\n");
        }

        if (references != null) {
            buf.append("<references>" + entities(references) + "</references>\n");
        }

        if (incidents != null) {
            buf.append("<incidents>" + entities(incidents) + "</incidents>\n");
        }

        // EasCapInfo blocks
        resetInfos();
        while (hasNextInfo()) {
            buf.append("<info>\n" + indent(getNextInfo().toString()) + "</info>\n");
        }

        // wrap the StringBuffer contents in the XML header and <alert> tags
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        if (stylesheetName != null) {
            xml = xml + "<?xml-stylesheet href=\"" + stylesheetName + "\" type=\"" + stylesheetType + "\"?>\n";
        }

        xml = xml + "<alert xmlns=\"" + nameSpace + "\">\n";
        xml = xml + indent(buf.toString());
        xml = xml + "</alert>\n";
        return xml;
    }

    /** return CAP 1.0 message */
    public String toCAP10() {
        buf = new StringBuffer();

        if (identifier != null) {
            buf.append("<identifier>" + entities(identifier) + "</identifier>\n");
        } else {
            buf.append("<identifier>" + entities(String.valueOf(new Date().getTime())) + "</identifier>\n");
        }

        if (sender != null) {
            buf.append("<sender>" + entities(sender) + "</sender>\n");
        } else {
            buf.append("<sender>" + entities("Unknown") + "</sender>\n");
        }

        if (sent != null) {
            buf.append("<sent>" + entities(sent) + "</sent>\n");
        } else {
            buf.append("<sent>" + entities(DateTime.getDateTime()) + "</sent>\n");
        }

        if (status != null) {
            buf.append("<status>" + entities(status) + "</status>\n");
        } else {
            buf.append("<status>" + entities("Test") + "</status>\n");
        }

        if (msgType != null) {
            buf.append("<msgType>" + entities(msgType) + "</msgType>\n");
        } else {
            buf.append("<msgType>" + entities("Alert") + "</msgType>\n");
        }

        if (password != null) {
            buf.append("<password>" + entities(password) + "</password>\n");
        }

        if (source != null) {
            buf.append("<source>" + entities(source) + "</source>\n");
        }

        if (scope != null) {
            buf.append("<scope>" + entities(scope) + "</scope>\n");
        } else {
            buf.append("<scope>" + entities("Public") + "</scope>\n");
        }

        if (restriction != null) {
            buf.append("<restriction>" + entities(restriction) + "</restriction>\n");
        }

        if (addresses != null) {
            buf.append("<addresses>" + entities(addresses) + "</addresses>\n");
        }

        resetCodes();
        while (hasNextCode()) {
            buf.append("<code>" + entities(getNextCode()) + "</code>\n");
        }

        if (note != null) {
            buf.append("<note>" + entities(note) + "</note>\n");
        }

        if (references != null) {
            buf.append("<references>" + entities(references) + "</references>\n");
        }

        if (incidents != null) {
            buf.append("<incidents>" + entities(incidents) + "</incidents>\n");
        }

        // EasCapInfo blocks
        resetInfos();
        while (hasNextInfo()) {
            buf.append("<info>\n" + indent(getNextInfo().toCAP10()) + "</info>\n");
        }

        // wrap the StringBuffer contents in the XML header and <alert> tags
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        xml = xml + "<alert xmlns=\"http://www.incident.com/cap/1.0\">\n";
        xml = xml + indent(buf.toString());
        xml = xml + "</alert>\n";
        return xml;
    }
    // STATIC UTILTY FUNCTIONS
    /** Utility to replace special characters with XML entities */
    public static String entities(String text) {
        text = text.replaceAll("&", "&amp;");
        text = text.replaceAll("<", "&lt;");
        text = text.replaceAll(">", "&gt;");
        text = text.replaceAll("\"", "&quot;");
        text = text.replaceAll("'", "&apos;");
        return text;
    }

    /** Utility to indent an XML fragment */
    public static String indent(String text) {
        StringTokenizer st = new StringTokenizer(text, "\n");
        StringBuffer buf = new StringBuffer();
        while (st.hasMoreTokens()) {
            buf.append(indentString + st.nextToken() + "\n");
        }
        return buf.toString();
    }

    /** Utility to linewrap text to 65 characters per line max */
    public static String linewrap(String string) {
        if (string == null) {
            return "";
        }
        StringTokenizer st = new StringTokenizer(string, "\n");
        String wrappedString = "";
        while (st.hasMoreTokens()) {
            String line = st.nextToken();
            String wrapped = "";
            int wrappedLength = 0;
            if (line.length() > 64) {
                line = line.replaceAll("\\.{3}", "... ");
                StringTokenizer st2 = new StringTokenizer(line);
                while (st2.hasMoreTokens()) {
                    String word = st2.nextToken();
                    if (wrappedLength + word.length() < 65) {
                        wrapped = wrapped + " " + word;
                        wrappedLength = wrappedLength + word.length();
                    } else {
                        wrapped = wrapped + "\n" + word;
                        wrappedLength = word.length();
                    }
                }
            } else {
                wrapped = line;
            }
            wrappedString = wrappedString + wrapped + "\n";
        }
        return wrappedString;
    }

    /** Load a text file */
    public static String loadFile(File file) {
        String content = null;
        String line = null;
        String lineSep = System.getProperty("line.separator");
        if (file != null) {
            try {
                BufferedReader in = new BufferedReader(new FileReader(file));
                StringBuffer buf = new StringBuffer();
                while ((line = in.readLine()) != null) {
                    buf.append(line + lineSep);
                }
                in.close();
                content = buf.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return content;
    }

    /** For testing only */
    public static void main(String[] args) {

        EasCapAlert thisAlert = new EasCapAlert();
        thisAlert.setStylesheetName("cap.xsl");
        EasCapInfo thisInfo = new EasCapInfo();
        thisAlert.addInfo(thisInfo);
        EasCapArea thisArea = new EasCapArea();
        thisArea.addGeocode("foo", "bar");
        thisArea.addGeocode("bas", "bah");
        thisArea.addPolygon("42,-124.2102 42,-120 39,-120 35.0,-114.6328 34.35,-114.1 33.108,-114.6259 33.0,-114.4 32.71,-114.4 32.7151,-114.7197 32.5338,-117.1247 34.28,-120.4418 38.9383,-123.817 40.4533,-124.4522 42,-124.2102");
        thisInfo.addArea(thisArea);
        EasCapResource thisResource = new EasCapResource();
        thisInfo.addResource(thisResource);

        String dotOneString = thisAlert.toString();
        System.out.println("1 - WRITING AS CAP 1.1:\n" + dotOneString);

        EasCapAlert nextAlert = EasCapAlert.parse(dotOneString);
        String dotZeroString = nextAlert.toCAP10();
        System.out.println("2 - REWRITING AS CAP 1.0\n" + dotZeroString);

        EasCapAlert thirdAlert = EasCapAlert.parse(dotZeroString);
        System.out.println("3 - REWRITING AS CAP 1.1:\n" + thirdAlert.toString());

        System.out.println("BOUNDING BOX: " + thirdAlert.getBounds());

    }
}
